package com.vlyman.rsa;

import com.vlyman.consts.KeyConsts;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;


/**
 * 类描述：
 *  RSA加密算法工具类
 * @author chenjs
 * @date 2019/1/11 12:51
 **/
@Slf4j
public class RsaUtil {

    /**
     * 方法描述：
     *   获取rsa私钥对象
     *  @author chenjs
     *  @param modulus 公共模数
     *  @param privateExponent 私钥指数
     *  @return rsa私钥
     *  @date 2019/1/11 12:52
     */
    public static RSAPrivateKey getPrivateKey(byte[] modulus, byte[] privateExponent) throws Exception {
        KeyFactory keyFac = KeyFactory.getInstance(KeyConsts.RSA_ALGORITHM, new BouncyCastleProvider());
        //通过modulus和privateExponent还原私钥对象
        RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
        return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
    }

    /**
     * 方法描述：
     *   获取rsa公钥对象
     *  @author chenjs
     *  @param modulus 公共模数
     *  @param publicExponent 公钥指数
     *  @return rsa公钥
     *  @date 2019/1/11 12:54
     */
    public static RSAPublicKey getPublicKey(byte[] modulus, byte[] publicExponent) throws Exception {
        KeyFactory keyFac = KeyFactory.getInstance(KeyConsts.RSA_ALGORITHM, new BouncyCastleProvider());
        //通过modulus，publicExponent还原公钥对象
        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent));
        return (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
    }

    /**
     * 方法描述：
     *   公钥加密
     *  @author chenjs
     *  @param data 待加密数据
     *  @param publicKey rsa公钥对象
     *  @return 加密后数据
     *  @date 2019/1/11 13:02
     */
    public static byte[] publicEncrypt(byte[] data, RSAPublicKey publicKey) throws Exception{
        try {
            Cipher cipher = Cipher.getInstance(KeyConsts.RSA_ALGORITHM, new BouncyCastleProvider());
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            int blockSize = cipher.getBlockSize();
            /**
             * 获得加密块大小，如：加密前数据为128个byte，而key_size=1024
             * 加密块大小为127byte，加密后为128个byte；因此共有2个加密块，第一个127byte，第二个为1个byte
             */
            // 获得加密块加密后块大小
            int outputSize = cipher.getOutputSize(data.length);
            int leavedSize = data.length % blockSize;
            int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize;
            byte[] raw = new byte[outputSize * blocksSize];
            int i = 0;
            while (data.length - i * blockSize > 0) {
                if (data.length - i * blockSize > blockSize) {
                    cipher.doFinal(data, i * blockSize, blockSize, raw, i * outputSize);
                } else {
                    cipher.doFinal(data, i * blockSize, data.length - i * blockSize, raw, i * outputSize);
                }
                /**
                 * 这里面doUpdate方法不可用，查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到
                 * ByteArrayOutputStream中，而最后doFinal的时候才将所有的byte[]进行加密，可是到了此时加密块大小很可能已经超出了
                 * OutputSize所以只好用dofinal方法。
                 */
                i++;
            }
            return raw;
        } catch (Exception e){
            log.error("加密字符串[" + new String(data) + "]时遇到异常", e);
            throw new Exception("RSA公钥加密出错！");
        }
    }

    /**
     * 方法描述：
     *   私钥解密
     *  @author chenjs
     *  @param data 待解密数据
     *  @param privateKey rsa私钥对象
     *  @return 解密后数据
     *  @date 2019/1/11 13:04
     */
    public static byte[] privateDecrypt(byte[] data, RSAPrivateKey privateKey){
        try {
            Cipher cipher = Cipher.getInstance(KeyConsts.RSA_ALGORITHM, new BouncyCastleProvider());
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            int blockSize = cipher.getBlockSize();
            ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
            int j = 0;
            while (data.length - j * blockSize > 0) {
                bout.write(cipher.doFinal(data, j * blockSize, blockSize));
                j++;
            }
            return bout.toByteArray();
        } catch (Exception e){
            log.error("解密字符串[" + new String(data) + "]时遇到异常", e);
            return null;
        }
    }

    public static void main(String[] args) {
        String data = "vlyman用户@chenjs";
        try {
            System.out.println("公钥加密");
            KeyPair keyPair = RsaKeyCreator.getKeyPair();
            String encryptData = Base64Utils.encodeToString(RsaUtil.publicEncrypt(data.getBytes(), (RSAPublicKey) keyPair.getPublic()));
            System.out.println("加密后数据(Base64转码)："+encryptData);
            System.out.println("私钥解密");
            byte[] decryptData = RsaUtil.privateDecrypt(Base64Utils.decodeFromString(encryptData), (RSAPrivateKey) keyPair.getPrivate());
            System.out.println("解密后数据："+new String(decryptData));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
